#include "stdafx.h"
#include "render/Application_base.h"
#include "render/Surface.h"
#include "render/render.h"
#include <allegro5/allegro.h>
#include <allegro5/allegro_font.h>
#include <allegro5/allegro_ttf.h>
#include <allegro5/allegro_primitives.h>
#include <allegro5/allegro_image.h>
#include <thread>

namespace App
{

MouseState g_mouseState;
ALLEGRO_KEYBOARD_STATE g_keyboardState;

const MouseState& GetMouseState()
{
	return g_mouseState;
}

static int g_keys[] = 
{
	ALLEGRO_KEY_A,
	ALLEGRO_KEY_B,
	ALLEGRO_KEY_C,
	ALLEGRO_KEY_D,
	ALLEGRO_KEY_E,
	ALLEGRO_KEY_F,
	ALLEGRO_KEY_G,
	ALLEGRO_KEY_H,
	ALLEGRO_KEY_I,
	ALLEGRO_KEY_J,
	ALLEGRO_KEY_K,
	ALLEGRO_KEY_L,
	ALLEGRO_KEY_M,
	ALLEGRO_KEY_N,
	ALLEGRO_KEY_O,
	ALLEGRO_KEY_P,
	ALLEGRO_KEY_Q,
	ALLEGRO_KEY_R,
	ALLEGRO_KEY_S,
	ALLEGRO_KEY_T,
	ALLEGRO_KEY_U,
	ALLEGRO_KEY_V,
	ALLEGRO_KEY_W,
	ALLEGRO_KEY_X,
	ALLEGRO_KEY_Y,
	ALLEGRO_KEY_Z,

	ALLEGRO_KEY_0,
	ALLEGRO_KEY_1,
	ALLEGRO_KEY_2,
	ALLEGRO_KEY_3,
	ALLEGRO_KEY_4,
	ALLEGRO_KEY_5,
	ALLEGRO_KEY_6,
	ALLEGRO_KEY_7,
	ALLEGRO_KEY_8,
	ALLEGRO_KEY_9,

	ALLEGRO_KEY_PAD_0,
	ALLEGRO_KEY_PAD_1,
	ALLEGRO_KEY_PAD_2,
	ALLEGRO_KEY_PAD_3,
	ALLEGRO_KEY_PAD_4,
	ALLEGRO_KEY_PAD_5,
	ALLEGRO_KEY_PAD_6,
	ALLEGRO_KEY_PAD_7,
	ALLEGRO_KEY_PAD_8,
	ALLEGRO_KEY_PAD_9,

	ALLEGRO_KEY_F1,	
	ALLEGRO_KEY_F2,	
	ALLEGRO_KEY_F3,	
	ALLEGRO_KEY_F4,	
	ALLEGRO_KEY_F5,	
	ALLEGRO_KEY_F6,	
	ALLEGRO_KEY_F7,	
	ALLEGRO_KEY_F8,	
	ALLEGRO_KEY_F9,	
	ALLEGRO_KEY_F10,	
	ALLEGRO_KEY_F11,	
	ALLEGRO_KEY_F12,	

	ALLEGRO_KEY_ESCAPE,		
	ALLEGRO_KEY_TILDE,		
	ALLEGRO_KEY_MINUS,		
	ALLEGRO_KEY_EQUALS,		
	ALLEGRO_KEY_BACKSPACE,	
	ALLEGRO_KEY_TAB,			
	ALLEGRO_KEY_OPENBRACE,	
	ALLEGRO_KEY_CLOSEBRACE,	
	ALLEGRO_KEY_ENTER,		
	ALLEGRO_KEY_SEMICOLON,	
	ALLEGRO_KEY_QUOTE,		
	ALLEGRO_KEY_BACKSLASH,	
	ALLEGRO_KEY_BACKSLASH2,	
	ALLEGRO_KEY_COMMA,		
	ALLEGRO_KEY_FULLSTOP,	
	ALLEGRO_KEY_SLASH,		
	ALLEGRO_KEY_SPACE,		

	ALLEGRO_KEY_INSERT,	
	ALLEGRO_KEY_DELETE,	
	ALLEGRO_KEY_HOME,	
	ALLEGRO_KEY_END,		
	ALLEGRO_KEY_PGUP,	
	ALLEGRO_KEY_PGDN,	
	ALLEGRO_KEY_LEFT,	
	ALLEGRO_KEY_RIGHT,	
	ALLEGRO_KEY_UP,		
	ALLEGRO_KEY_DOWN,	

	ALLEGRO_KEY_PAD_SLASH,	
	ALLEGRO_KEY_PAD_ASTERISK,
	ALLEGRO_KEY_PAD_MINUS,	
	ALLEGRO_KEY_PAD_PLUS,	
	ALLEGRO_KEY_PAD_DELETE,	
	ALLEGRO_KEY_PAD_ENTER,	

	ALLEGRO_KEY_LSHIFT,	
	ALLEGRO_KEY_RSHIFT,	
	ALLEGRO_KEY_LCTRL,	
	ALLEGRO_KEY_RCTRL,	
	ALLEGRO_KEY_ALT,		
	ALLEGRO_KEY_ALTGR,	
	ALLEGRO_KEY_LWIN,
	ALLEGRO_KEY_RWIN,
	ALLEGRO_KEY_MENU,	
	ALLEGRO_KEY_SCROLLLOCK,
	ALLEGRO_KEY_NUMLOCK,
	ALLEGRO_KEY_CAPSLOCK,
};

KeyCode GetKeyCode(int allegrokeyCode)
{
	for (int i = 0; i < sizeof(g_keys) / sizeof(int); i++)
		if (g_keys[i] == allegrokeyCode)
			return (KeyCode) i;

	return KeyCode::KEY_UNKNOWN;
}

int AllegroKeyCode(KeyCode keyCode)
{
	return g_keys[keyCode];
}

bool KeyDown(KeyCode keyCode)
{
	return al_key_down(&g_keyboardState, AllegroKeyCode(keyCode));
}

struct Event
{
	ALLEGRO_EVENT* pEvent;
};

EventType GetType(const Event* pEvent)
{
	switch(pEvent->pEvent->type) 
	{
		case ALLEGRO_EVENT_KEY_CHAR: return EventType::EVENT_KEY_DOWN;
	}

	return EventType::EVENT_UNKNOWN;
}

KeyDownEvent GetKeyDownEvent(const Event* pEvent)
{
	KeyDownEvent event;
	event.unicode = pEvent->pEvent->keyboard.unichar;
	event.keyCode = GetKeyCode(pEvent->pEvent->keyboard.keycode);
	return event;
}

//////////////////////////////////////////////////////////////

struct LibData
{
	LibData() : pDisplay(0), pEventQueue(0) {}

	ALLEGRO_DISPLAY* pDisplay;
	ALLEGRO_EVENT_QUEUE* pEventQueue;
};

Application::Application() : m_pLibData(new LibData)
{
	m_running = true;
	m_videoWidth = 640;
	m_videoHeight = 480;
	m_appName = "App";
}

int Application::OnExecute() 
{
	if(OnInit_() == false) 
	{
		return -1;
	}

	while(m_running) 
	{
		uint64 startTime = Ut::GetTime();

		ALLEGRO_MOUSE_STATE mouseState;
		al_get_mouse_state(&mouseState);
		g_mouseState.x = mouseState.x;
		g_mouseState.y = mouseState.y;
		g_mouseState.left = (mouseState.buttons & 1) != 0;
		g_mouseState.right = (mouseState.buttons & 2) != 0;

		al_get_keyboard_state(&g_keyboardState);

		ALLEGRO_EVENT allegroEvent;
 
		while(al_get_next_event(m_pLibData->pEventQueue, &allegroEvent)) 
		{
			if (allegroEvent.type == ALLEGRO_EVENT_DISPLAY_CLOSE)
				OnExit();

			if (allegroEvent.type == ALLEGRO_EVENT_DISPLAY_RESIZE)
			{
				m_videoWidth = allegroEvent.display.width;
				m_videoHeight = allegroEvent.display.height;
				al_resize_display(m_pLibData->pDisplay, m_videoWidth, m_videoHeight);
			}

			Event event;
			event.pEvent = &allegroEvent;
		
			OnEvent(&event);
		}

		OnUpdate_();
		OnRender_();

		m_frameCount++;
		float sleepTime;

		{
			int total = (int) (GetRenderTimeMs() + GetUpdateTimeMs());

			uint64 startTime = Ut::GetTime();

			if (total < 15)
			{
				std::chrono::milliseconds ms(15 - total);
				std::this_thread::sleep_for(ms);
			}

			uint64 endTime = Ut::GetTime();
			sleepTime = Ut::GetMs(endTime - startTime);
		}

		uint64 endTime = Ut::GetTime();
		m_frameTimeMs = Ut::GetMs(endTime - startTime);
	}

	OnCleanup_();

	return 0;
}

void Application::SetVideoMode(int width, int height)
{
	m_videoWidth = width;
	m_videoHeight = height;
}

void Application::GetVideoMode(int& width, int& height)
{
	width = m_videoWidth;
	height = m_videoHeight;
}

void Application::SetAppName(const char* name)
{
	m_appName = name;
}

bool Application::OnInit_() 
{
	if (!al_init()) 
		return false;

	al_set_new_display_flags(ALLEGRO_WINDOWED|ALLEGRO_RESIZABLE);
	al_set_new_display_option(ALLEGRO_VSYNC, 1, ALLEGRO_SUGGEST);

	al_set_new_display_option(ALLEGRO_SAMPLE_BUFFERS, 1, ALLEGRO_SUGGEST);
	al_set_new_display_option(ALLEGRO_SAMPLES, 4, ALLEGRO_SUGGEST);

	m_pLibData->pDisplay = al_create_display(m_videoWidth, m_videoHeight);

	if (!m_pLibData->pDisplay) 
		return false;

	al_set_window_title(m_pLibData->pDisplay, m_appName.c_str());

	al_clear_to_color(al_map_rgb(0,0,0));

	m_pLibData->pEventQueue = al_create_event_queue();

	if (!m_pLibData->pEventQueue) 
		return false;
 
	al_register_event_source(m_pLibData->pEventQueue, al_get_display_event_source(m_pLibData->pDisplay));

	if (!al_install_mouse())
		return false;

	al_init_font_addon();
	al_init_ttf_addon();
	al_init_primitives_addon();
	al_init_image_addon();
	al_install_keyboard();

	al_register_event_source(m_pLibData->pEventQueue, al_get_keyboard_event_source());
 
	if (OnInit() == false)
		return false;

	m_frameCount = 0;

	return true;
}

void Application::OnExit() 
{
    m_running = false;
}

void Application::OnUpdate_() 
{
	uint64 startTime = Ut::GetTime();

	OnUpdate();

	uint64 endTime = Ut::GetTime();
	m_updateTimeMs = Ut::GetMs(endTime - startTime);
}

void Application::OnRender_() 
{
	uint64 startTime = Ut::GetTime();

	OnRender();

	uint64 endTime = Ut::GetTime();
	m_renderTimeMs = Ut::GetMs(endTime - startTime);

	al_flip_display();
}

float Application::GetFrameTimeMs() const
{
	return m_frameTimeMs;
}

float Application::GetUpdateTimeMs() const
{
	return m_updateTimeMs;
}

float Application::GetRenderTimeMs() const
{
	return m_renderTimeMs;
}

uint32 Application::GetFrameCount() const
{
	return m_frameCount;
}

void Application::OnCleanup_() 
{
	OnCleanup();

	al_destroy_event_queue(m_pLibData->pEventQueue);
	al_destroy_display(m_pLibData->pDisplay);
}

Render::SurfacePtr Application::MainView()
{
	return (Render::SurfacePtr) al_get_backbuffer(m_pLibData->pDisplay);
}

}